package com.merry.jedis.service;

import com.merry.jedis.bean.Order;
import com.merry.jedis.bean.User;
import com.merry.jedis.mapper.UserMapper;
import com.sun.media.jfxmedia.logging.Logger;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.Resource;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

@Service
public class OrderService {

    public static final String ORDER_KEY = "ord:";

    /**
     * redisTemplate 的序列化问题：
     * 键（key）和值（value）都是通过spring踢狗额serializer序列化到数据库的。
     * redisTemplate默认使用的是JDKSerializationRedisSerializer（这是在RedisTemplate#afterPropertiesSet()方法中配置生效的），
     * StringRedisTemplate 默认使用的是 StringRedisSerializer，
     * 所以我们想要解决redis的key和value乱码问题：注意value在redis中会将中文改成乱码的格式，需要显示原始中文，需要在登录时添加： redis-cli --raw.
     * 1.统一使用 StringRedisTemplate 代替 RedisTemplate。
     * 2.在RedisConfig类中自己配置系列化类。
     */

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private UserMapper userMapper;

    public void addOrder(){
        int keyId = ThreadLocalRandom.current().nextInt(1000)+1;
        String serialNo = UUID.randomUUID().toString();

        String key= ORDER_KEY+keyId;
        String value = "order"+serialNo;
        redisTemplate.opsForValue().set(key,value);
    }

    public String getOrderById(Integer keyId){
        return (String) redisTemplate.opsForValue().get(ORDER_KEY+keyId);
    }


    /**
     * 使用双检加锁策略处理redis和MySQL缓存一致性的问题。
     * 这个操作类似于DCL的单例解决思路。
     */
    public User updateOrder(String id){
        User user = null;
        String key = ORDER_KEY+id;

        //1.先从redis里面查询，如果有直接返回结果，如果没有再去查询mysql
        user = (User) redisTemplate.opsForValue().get(key);

        if(null == user){
            //2.加锁，保证一次只有一个请求进来，让外面的redis等待一下，避免击穿MySQL。
            synchronized (OrderService.class){
                user = (User) redisTemplate.opsForValue().get(key);
                //3.二次查询redis，如果还是null，则去查询MySQL
                if(user == null){
                    //4.查询MySQL中的数据。
                    user = userMapper.selectByPrimaryKey(id);
                    if(user == null){
                        return null;
                    }else{
                        //5.将MySQL中的数据回写到redis中，保证数据的一致性。
                        redisTemplate.opsForValue().setIfAbsent(key,user,7L, TimeUnit.DAYS);
                    }
                }
            }
        }
        return user;
    }


    /**
     * 延迟双删
     * 第二次删除的作用就是为了让后续的线程同步这个数据时，同步的是最新的数据。
     * 休眠时间是多少呢？ 休眠时间应该大于另一个线程读取数据再写入到缓存中的时间。
     */
    public void deleteOrderData(Order order){
        //1.第一次成功删除redis缓存。
        redisTemplate.delete("id");
        //2.更新MySQL数据库
        userMapper.update(new User());


        //采用异步的方式实现延迟双删，这样不会降低主线程的吞吐量。
        CompletableFuture.supplyAsync(()->{
            //3.暂停一段时间。达到延迟的效果
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //4.第二次删除，达到延迟双删的效果。
            return redisTemplate.delete("id");
        }).whenComplete((t,u)->{
            System.out.println(t);
        }).exceptionally(e->{
            System.out.println(e.getMessage());
            return false;
        });
    }

}
